﻿#region Copyrights

/*
    - - - - - - - - - - - - - - - - - - - - - - -
    Nome do Arquivo : DMOBase.cs
    - - - - - - - - - - - - - - - - - - - - - - -

    Sistema				    :  	LLS.Core
    Modulo				    :  	LLS.DataModel.Core
    Autor				    :	Leonardo L. de Siqueira
    Data					:	27/02/2012
    Descrição  				:	Classe abstrata base para todos os objetos de modelo dados.
    
*/

#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Reflection;

namespace LLS.DataModel.Core
{
    /// <summary>
    /// Classe abstrata base para todos os objetos de modelo dados.
    /// </summary>
    public abstract class DMOBase
    {
        #region Classes

        /// <summary>
        /// Classe para armazenamento das informações de uma propriedade de um modelo de dados
        /// </summary>
        public class PropertyInformation
        {
            #region Properties

            /// <summary>
            /// Resgata / define o nome da propriedade do modelo de dados
            /// </summary>
            public string PropertyName { get; set; }

            /// <summary>
            /// Resgata / define o valor da propriedade do modelo de dados
            /// </summary>
            public object PropertyValue { get; set; }

            /// <summary>
            /// Resgata / define o ColumnAttribute associado à propriedade do modelo de dados
            /// </summary>
            public ColumnAttribute ColumnAttribute { get; set; }

            #endregion

            #region Métodos

            /// <summary>
            /// Método construtor
            /// </summary>
            /// <param name="propertyName">Nome da propriedade do modelo de dados</param>
            public PropertyInformation(string propertyName)
            {
                PropertyName = propertyName;
                PropertyValue = null;
                ColumnAttribute = null;
            }

            /// <summary>
            /// Método construtor
            /// </summary>
            /// <param name="propertyName">Nome da propriedade do modelo de dados</param>
            /// <param name="propertyValue">Valor da propriedade do modelo de dados</param>
            public PropertyInformation(string propertyName, object propertyValue)
            {
                PropertyName = propertyName;
                PropertyValue = propertyValue;
                ColumnAttribute = null;
            }

            /// <summary>
            /// Método construtor
            /// </summary>
            /// <param name="propertyName">Nome da propriedade do modelo de dados</param>
            /// <param name="propertyValue">Valor da propriedade do modelo de dados</param>
            /// <param name="columnAttribute">ColumnAttribute associado à propriedade do modelo de dados</param>
            public PropertyInformation(string propertyName, object propertyValue, ColumnAttribute columnAttribute)
            {
                PropertyName = propertyName;
                PropertyValue = propertyValue;
                ColumnAttribute = columnAttribute;
            }

            /// <summary>
            /// Retorno o valor da propriedade PropertyValue em formato string transformando valores boleanos em 0 e 1 e enum em números.
            /// </summary>
            /// <returns>Valor convertido para string</returns>
            public override string ToString()
            {
                string ret = null;

                if ((PropertyValue.GetType().IsEnum) || (PropertyValue.GetType().Equals(typeof(Boolean))) ||
                    ((System.Nullable.GetUnderlyingType(PropertyValue.GetType()) != null) && (System.Nullable.GetUnderlyingType(PropertyValue.GetType()).IsEnum)) ||
                    ((System.Nullable.GetUnderlyingType(PropertyValue.GetType()) != null) && (System.Nullable.GetUnderlyingType(PropertyValue.GetType()).Equals(typeof(Boolean)))))
                    ret = Convert.ToInt32(PropertyValue).ToString();
                else if ((PropertyValue == null) || (PropertyValue == DBNull.Value))
                    ret = null;
                else
                    ret = PropertyValue.ToString();

                return ret;
            }

            #endregion
        }

        /// <summary>
        /// Classe de lista para objetos do tipo PropertyInformation
        /// </summary>
        public class PropertiesInformation : List<PropertyInformation>
        {
            #region Métodos

            /// <summary>
            /// Localiza um objeto do tipo PropertyInformation de acordo com o nome da coluna informada
            /// </summary>
            /// <param name="columnName">Nome da coluna para localização</param>
            /// <returns>Objeto do tipo PropertyInformation ou null em caso de não localização.</returns>
            public PropertyInformation FindByColumnName(string columnName)
            {
                foreach (PropertyInformation info in this)
                {
                    if (info.ColumnAttribute.Name == columnName)
                        return info;
                }

                return null;
            }

            #endregion
        }

        #endregion

        #region Fields

        Type _type = null;
        private string _tableName = null;
        private bool? _isNotAudit = null;
        private static Dictionary<Type, Dictionary<PropertyInfo, ColumnAttribute>> _modelProperties = null;

        #endregion

        #region Methods

        /// <summary>
        /// Método construtor
        /// </summary>
        public DMOBase()
        {
            _type = this.GetType();
        }

        /// <summary>
        /// Método responsável por resgatar o nome da tabela do modelo de dados
        /// </summary>
        /// <returns>String contendo o nome da tabela</returns>
        public string GetTableName()
        {
            if (string.IsNullOrEmpty(_tableName))
            {
                object[] attributes = _type.GetCustomAttributes(typeof(TableAttribute), false);

                if (attributes != null)
                {
                    foreach (object attribute in attributes)
                    {
                        _tableName = ((TableAttribute)attribute).Name;
                        break;
                    }
                }
            }

            return _tableName;
        }

        /// <summary>
        /// Método responsável por resgatar o se a tabela não será auditada
        /// </summary>
        /// <returns>True ou false definindo se não será auditada</returns>
        public bool IsNotAudit()
        {
            if (!_isNotAudit.HasValue)
            {
                object[] attributes = _type.GetCustomAttributes(typeof(TableAttribute), false);

                if (attributes != null)
                {
                    foreach (object attribute in attributes)
                    {
                        _isNotAudit = ((TableAttribute)attribute).NotAudit;
                        break;
                    }
                }
            }

            if (_isNotAudit.HasValue)
                return _isNotAudit.Value;
            else
                return false;
        }

        /// <summary>
        /// Método responsável por resgatar o nome da coluna de tipo chave primária do modelo de dados
        /// </summary>
        /// <returns>Nome da coluna</returns>
        public string GetPrimaryKeyColumnName()
        {
            string ret = string.Empty;
            Dictionary<PropertyInfo, ColumnAttribute> modelProperties = GetModelProperties();

            foreach (PropertyInfo property in modelProperties.Keys)
            {
                if (modelProperties[property].PrimaryKey)
                    return modelProperties[property].Name;
            }

            return ret;
        }

        /// <summary>
        /// Método responsável por resgatar o nome da coluna do modelo de dados
        /// </summary>
        /// <param name="propertyName">Nome da propriedade</param>
        /// <returns>Nome da coluna</returns>
        public string GetColumnName(string propertyName)
        {
            string ret = string.Empty;
            Dictionary<PropertyInfo, ColumnAttribute> modelProperties = GetModelProperties();

            foreach (PropertyInfo property in modelProperties.Keys)
            {
                if (property.Name == propertyName)
                    return modelProperties[property].Name;
            }

            return ret;
        }

        /// <summary>
        /// Método responsável por resgatar o tipo da coluna do modelo de dados
        /// </summary>
        /// <param name="propertyName">Nome da propriedade</param>
        /// <returns>Tipo da coluna</returns>
        public DbType GetColumnType(string propertyName)
        {
            DbType ret = DbType.String;
            Dictionary<PropertyInfo, ColumnAttribute> modelProperties = GetModelProperties();

            foreach (PropertyInfo property in modelProperties.Keys)
            {
                if (property.Name == propertyName)
                    return modelProperties[property].Type;
            }

            return ret;
        }

        /// <summary>
        /// Método responsável por resgatar o tamanho da coluna do modelo de dados
        /// </summary>
        /// <param name="propertyName">Nome da propriedade</param>
        /// <returns>Tipo da coluna</returns>
        public int GetColumnSize(string propertyName)
        {
            int ret = 0;
            Dictionary<PropertyInfo, ColumnAttribute> modelProperties = GetModelProperties();

            foreach (PropertyInfo property in modelProperties.Keys)
            {
                if (property.Name == propertyName)
                    return modelProperties[property].Size;
            }

            return ret;
        }

        /// <summary>
        /// Método responsável por resgatar as informações das propriedades do modelo de dados tais como nome, valor e atributo.
        /// </summary>
        /// <returns>Lista genérica de objetos do tipo PropertyInformation com as informações resgatadas</returns>
        public PropertiesInformation GetPropertiesInformation()
        {
            PropertiesInformation ret = new PropertiesInformation();
            Dictionary<PropertyInfo, ColumnAttribute> modelProperties = GetModelProperties();

            foreach (PropertyInfo property in modelProperties.Keys)
            {
                object value = property.GetValue(this, null);
                ret.Add(new PropertyInformation(property.Name, (value != null) ? value : DBNull.Value, modelProperties[property]));
            }

            return ret;
        }

        /// <summary>
        /// Método responsável por definir o valor de uma propriedade do modelo de dados
        /// </summary>
        /// <param name="ColumnName">Nome da coluna</param>
        /// <param name="propertyValue">Valor da propriedade</param>
        public void SetPropertyValue(string ColumnName, object propertyValue)
        {
            Dictionary<PropertyInfo, ColumnAttribute> modelProperties = GetModelProperties();

            foreach (PropertyInfo property in modelProperties.Keys)
            {
                if (modelProperties[property].Name == ColumnName)
                {
                    if (property.PropertyType.IsEnum)
                        property.SetValue(this, Enum.ToObject(property.PropertyType, propertyValue), null);
                    else if ((System.Nullable.GetUnderlyingType(property.PropertyType) != null) && (System.Nullable.GetUnderlyingType(property.PropertyType).IsEnum))
                        property.SetValue(this, Enum.ToObject(System.Nullable.GetUnderlyingType(property.PropertyType), propertyValue), null);
                    else if (System.Nullable.GetUnderlyingType(property.PropertyType) != null)
                        property.SetValue(this, Convert.ChangeType(propertyValue, System.Nullable.GetUnderlyingType(property.PropertyType)), null);
                    else
                        property.SetValue(this, Convert.ChangeType(propertyValue, property.PropertyType), null);

                    return;
                }
            }
        }

        /// <summary>
        /// Método para resgatar as propriedades e atributos do modelo de dados
        /// </summary>
        /// <returns>Dicionário de dados com as propriedades e atributos do modelo de dados</returns>
        private Dictionary<PropertyInfo, ColumnAttribute> GetModelProperties()
        {
            if (_modelProperties == null)
                _modelProperties = new Dictionary<Type, Dictionary<PropertyInfo, ColumnAttribute>>();

            if (_modelProperties.ContainsKey(_type))
                return _modelProperties[_type];
            else
            {
                Dictionary<PropertyInfo, ColumnAttribute> modelProperties = new Dictionary<PropertyInfo, ColumnAttribute>();
                PropertyInfo[] properties = _type.GetProperties(BindingFlags.Public | BindingFlags.Instance);

                foreach (PropertyInfo info in properties)
                {
                    object[] attributes = info.GetCustomAttributes(typeof(ColumnAttribute), false);

                    if (attributes != null)
                        modelProperties.Add(info, (ColumnAttribute)attributes[0]);
                }

                _modelProperties.Add(_type, modelProperties);
                return modelProperties;
            }
        }

        #endregion
    }
}
